/*
 * Copyright (C) 2003-2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.reflext.core;

import org.reflext.api.ClassTypeInfo;
import org.reflext.api.MethodInfo;
import org.reflext.api.MethodSignature;
import org.reflext.api.TypeInfo;
import org.reflext.api.TypeVariableInfo;
import org.reflext.api.ParameterizedTypeInfo;
import org.reflext.api.ClassKind;
import org.reflext.api.annotation.AnnotationType;

import java.util.List;
import java.util.ArrayList;

/**
 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
 * @version $Revision$
 */
class ClassTypeInfoImpl<T, M, A, P> extends AbstractTypeInfo<T, M, A, P> implements ClassTypeInfo {

  /** . */
  private final T classType;

  /** . */
  private final String className;

  /** . */
  private List<TypeInfo> interfaces;

  /** . */
  private TypeInfo superType;

  /** . */
  private boolean superClassResolved;

  /** . */
  private List<TypeVariableInfo> typeParameters;

  /** . */
  private List<MethodInfo> methods;

  /** . */
  private final String simpleName;

  /** . */
  private final String packageName;

  /** . */
  private final ClassKind kind;

  /** . */
  private AnnotatedDelegate<T, M, A, P, T> annotatedDelegate;

  public ClassTypeInfoImpl(TypeResolverImpl<T, M, A, P> domain, T classType) {
    super(domain);

    //
    String className = domain.typeModel.getClassName(classType);
    String simpleName;
    String packageName;
    int index = className.lastIndexOf('.');
    if (index == -1) {
      simpleName = className;
      packageName = "";
    } else {
      simpleName = className.substring(index + 1);
      packageName = className.substring(0, index);
    }
    ClassKind kind = domain.typeModel.getClassKind(classType);

    //
    this.className = className;
    this.classType = classType;
    this.interfaces = null;
    this.superType = null;
    this.superClassResolved = false;
    this.simpleName = simpleName;
    this.packageName = packageName;
    this.kind = kind;
    this.annotatedDelegate = null;
  }

  public String getName() {
    return className;
  }

  public String getPackageName() {
    return packageName;
  }

  public String getSimpleName() {
    return simpleName;
  }

  public ClassKind getKind() {
    return kind;
  }

  public List<TypeVariableInfo> getTypeParameters() {
    if (typeParameters == null) {
      ArrayList<TypeVariableInfo> typeParameters = new ArrayList<TypeVariableInfo>();
      for (T tv : domain.typeModel.getTypeParameters(classType)) {
        TypeVariableInfoImpl<T, M, A, P> typeParameter = (TypeVariableInfoImpl<T, M, A, P>)domain._getType(tv);
        typeParameters.add(typeParameter);
      }
      this.typeParameters = typeParameters;
    }
    return typeParameters;
  }

  public Iterable<TypeInfo> getInterfaces() {
    if (interfaces == null) {
      ArrayList<TypeInfo> interfaces = new ArrayList<TypeInfo>();
      for (T interfaceType : domain.typeModel.getInterfaces(classType)) {
        TypeInfo itf = domain.resolve(interfaceType);
        interfaces.add(itf);
      }
      this.interfaces = interfaces;
    }
    return interfaces;
  }

  public TypeInfo getSuperType() {
    if (!superClassResolved) {
      T superClassType = domain.typeModel.getSuperClass(classType);
      if (superClassType != null) {
        superType = domain.resolve(superClassType);
      }
      superClassResolved = true;
    }
    return superType;
  }

  public TypeInfo resolve(TypeInfo type) {
    return Utils.resolve(this, type);
  }

  public List<MethodInfo> getDeclaredMethods() {
    if (methods == null) {
      List<MethodInfo> methods = new ArrayList<MethodInfo>();
      for (M method : domain.methodModel.getDeclaredMethods(classType)) {
        MethodInfo mi = new MethodInfoImpl<T, M, A, P>(this, domain, method);
        methods.add(mi);
      }
      this.methods = methods;
    }
    return methods;
  }

  public MethodInfo getDeclaredMethod(MethodSignature methodSignature) {
    for (MethodInfo methodInfo : getDeclaredMethods()) {
      if (methodInfo.getSignature().equals(methodSignature)) {
        return methodInfo;
      }
    }
    return null;
  }

  public ClassTypeInfo getSuperClass() {
    TypeInfo superType = getSuperType();
    if (superType instanceof ClassTypeInfo) {
      return (ClassTypeInfo)superType;
    } else if (superType instanceof ParameterizedTypeInfo) {
      TypeInfo rawType = ((ParameterizedTypeInfo)superType).getRawType();
      if (rawType instanceof ClassTypeInfo) {
        return (ClassTypeInfo)rawType;
      } else {
        throw new AssertionError("Cannot cast raw type " + rawType + " to class type");
      }
    } else {
      throw new AssertionError("Cannot cast type " + superType + " to class type");
    }
  }

  private boolean isAssignableFrom(TypeInfo that) {
    if (that instanceof ClassTypeInfo) {
      return isAssignableFrom((ClassTypeInfo)that);
    } else if (that instanceof ParameterizedTypeInfo) {
      return isAssignableFrom(((ParameterizedTypeInfo)that).getRawType());
    } else if (that instanceof TypeVariableInfo) {
      return isAssignableFrom(((TypeVariableInfo)that).getBounds().get(0));
    } else {
      return false;
    }
  }

  public boolean isAssignableFrom(ClassTypeInfo that) {
    if (className.equals("java.lang.Object")) {
      return true;
    }
    if (className.equals(that.getName())) {
      return true;
    }
    TypeInfo superType = that.getSuperType();
    if (superType != null && isAssignableFrom(superType)) {
      return true;
    }
    for (TypeInfo itf : that.getInterfaces()) {
      if (isAssignableFrom(itf)) {
        return true;
      }
    }
    return false;
  }

  public T getType() {
    return classType;
  }

  public <AT> AT getDeclaredAnnotation(AnnotationType<AT, ?> annotationType) {
    if (annotatedDelegate == null) {
      annotatedDelegate = new AnnotatedDelegate<T,M,A,P,T>(domain, domain.typeAnnotationModel, classType);
    }
    return annotatedDelegate.getDeclaredAnnotation(classType, annotationType);
  }

  public int hashCode() {
    return className.hashCode();
  }

  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }
    if (obj instanceof ClassTypeInfo) {
      ClassTypeInfo that = (ClassTypeInfo)obj;
      String thatName = that.getName();
      return className.equals(thatName);
    }
    return false;
  }

  @Override
  public String toString() {
    return "ClassTypeInfo[className=" + className + "]";
  }
}
